<template>
	<div ref="rootElement" class="ui-slip" :style="rootStyle">
		<div 
			:class="['ui-slip_inner', `is_${placement}`]" 
			@mousedown.prevent="down" 
			@touchstart.prevent="down">
			<slot></slot>
		</div>
	</div>
</template>

<script setup>
	
	import { ref, computed } from 'vue';
	
	const props = defineProps({
		modelValue: Object,
		unit: {
			type: [String, Array],
			default: 'px',
			validator: v => ['px', '%'].includes(v)
		},
		placement: {
			type: String,
			default: 'cc',
			validator: v => ['cc', 'tc', 'rc', 'bc', 'lc', 'tl', 'tr', 'bl', 'br'].includes(v)
		},
		wrap: Boolean
	})
	
	const emits = defineEmits(['update:modelValue', 'down', 'move', 'up']);
	
	const rootElement = ref();
	
	const unUnit = computed(() => {
		const isArrayUnit = Array.isArray(props.unit);
		const u = {
			x: isArrayUnit ? props.unit[0] : props.unit,
			y: isArrayUnit ? props.unit[1] : props.unit
		}
		return { ...u, xp: u.x == 'px' , yp: u.y == 'px'}
	})
	
	const rootStyle = computed(() => {
		return {
			left: `${props.modelValue?.x}${unUnit.value.x}`,
			top: `${props.modelValue?.y}${unUnit.value.y}`
		}
	})
	
	let snapshot = null;
	
	function down(e) {
		if (e.type === 'touchstart') {
			e.clientX = e.touches[0].clientX;
			e.clientY = e.touches[0].clientY;
		}
		const parentRect = rootElement.value.offsetParent.getBoundingClientRect();
		const childRect = rootElement.value.firstElementChild.getBoundingClientRect();
		snapshot = { 
			downX: e.clientX, 
			downY: e.clientY,
			offsetX: rootElement.value.offsetLeft,
			offsetY: rootElement.value.offsetTop,
			top: childRect.top - parentRect.top,
			left: childRect.left - parentRect.left,
			bottom: parentRect.bottom - childRect.bottom,
			right: parentRect.right - childRect.right,
			parentRect,
			childRect
		};
		window.addEventListener('mousemove', move, { passive: true });
		window.addEventListener('touchmove', move, { passive: true });
		window.addEventListener('mouseup', up, { passive: true });
		window.addEventListener('touchend', up, { passive: true });
		emits('update:modelValue', calc(e));
		emits('down');
	}
	
	function move(e) {
		if (e.type === 'touchmove') {
			e.clientX = e.touches[0].clientX;
			e.clientY = e.touches[0].clientY;
		}
		emits('update:modelValue', calc(e));
		emits('move');
	}
	
	function up(e) {
		window.removeEventListener('mousemove', move);
		window.removeEventListener('touchmove', move);
		window.removeEventListener('mouseup', up);
		window.removeEventListener('touchend', up);
		if (e.type === 'touchmove') {
			e.clientX = e.touches[0].clientX;
			e.clientY = e.touches[0].clientY;
		}
		emits('update:modelValue', calc(e));
		emits('up');
	}
	
	function calc({ clientX, clientY }) {
		const { 
			downX, downY, offsetX, offsetY,
			parentRect, childRect, 
			top, left, bottom, right 
		} = snapshot;
		const distx = clientX - downX, disty = clientY - downY;
		const pos = {
			x: offsetX + distx,
			y: offsetY + disty
		};
		if (props.wrap) {
			if (-disty > top) {
				pos.y = offsetY - top;
			} else if (disty > bottom) {
				pos.y = offsetY + bottom;
			}
			if (-distx > left) {
				pos.x = offsetX - left;
			} else if (distx > right) {
				pos.x = offsetX + right;
			}
		}
		return {
			x: unUnit.value.xp ? pos.x : (pos.x * 100 / parentRect.width || 1),
			y: unUnit.value.yp ? pos.y : (pos.y * 100 / parentRect.height || 1)
		}
	}
	
</script>

<style lang="scss" scoped>
	
	.ui-slip, .ui-slip_inner {
		position: absolute;
		touch-action: none;
	}
	.ui-slip_inner {
		&.is_cc {
			top: 0;
			left: 0;
			transform: translate(-50%, -50%);
		}
		&.is_tc {
			bottom: 0;
			left: 0;
			transform: translate(-50%, 0);
		}
		&.is_rc {
			top: 0;
			left: 0;
			transform: translate(0, -50%);
		}
		&.is_bc {
			top: 0;
			left: 0;
			transform: translate(-50%, 0);
		}
		&.is_lc {
			top: 0;
			right: 0;
			transform: translate(0, -50%);
		}
		&.is_tl {
			bottom: 0;
			right: 0;
		}
		&.is_tr {
			bottom: 0;
			left: 0;
		}
		&.is_bl {
			top: 0;
			right: 0;
		}
		&.is_br {
			top: 0;
			left: 0;
		}
	}
	
</style>